#include "dds_loader.h"
#ifdef _WIN32
#pragma comment(lib, "OpenGL32.lib")
#endif
#include <fstream>
using std::ifstream;

//Declare all base types... 
#ifndef DWORD
typedef unsigned long int DWORD;
#endif
#ifndef WORD
typedef unsigned short WORD;
#endif
#ifndef LONG
typedef long LONG;
#endif
#ifndef LPVOID
typedef void *LPVOID;
#endif

#ifndef MAKEFOURCC
#define MAKEFOURCC(ch0, ch1, ch2, ch3)                              \
                ((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) |   \
                ((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24 ))
#endif

#ifndef __DDRAW_INCLUDED__

#define FOURCC_DXT1  (MAKEFOURCC('D','X','T','1'))
#define FOURCC_DXT2  (MAKEFOURCC('D','X','T','2'))
#define FOURCC_DXT3  (MAKEFOURCC('D','X','T','3'))
#define FOURCC_DXT4  (MAKEFOURCC('D','X','T','4'))
#define FOURCC_DXT5  (MAKEFOURCC('D','X','T','5'))

/*The dwFlags member of the original DDSURFACEDESC2 structure 
can be set to one or more of the following values:*/
#define	DDSD_CAPS				0x00000001
#define	DDSD_HEIGHT				0x00000002 
#define	DDSD_WIDTH				0x00000004 
#define	DDSD_PITCH				0x00000008 
#define	DDSD_PIXELFORMAT		0x00001000 
#define	DDSD_MIPMAPCOUNT		0x00020000 
#define	DDSD_LINEARSIZE			0x00080000 
#define	DDSD_DEPTH				0x00800000 

/*The ddpfPixelFormat member of the DDSURFACEDESC2 
structure can be set to one or more of the following values:*/
#define	DDPF_ALPHAPIXELS		0x00000001 
#define	DDPF_FOURCC				0x00000004 
#define	DDPF_RGB				0x00000040 

/*The dwCaps1 member of the DDSCAPS2 structure 
can be set to one or more of the following values:*/
#define	DDSCAPS_COMPLEX			0x00000008 
#define	DDSCAPS_TEXTURE			0x00001000 
#define	DDSCAPS_MIPMAP			0x00400000 

/*The dwCaps2 member of the DDSCAPS2 structure can 
be set to one or more of the following values:*/
#define	DDSCAPS2_CUBEMAP		0x00000200 
#define	DDSCAPS2_CUBEMAP_POSITIVEX 0x00000400 
#define	DDSCAPS2_CUBEMAP_NEGATIVEX 0x00000800 
#define	DDSCAPS2_CUBEMAP_POSITIVEY 0x00001000 
#define	DDSCAPS2_CUBEMAP_NEGATIVEY 0x00002000 
#define	DDSCAPS2_CUBEMAP_POSITIVEZ 0x00004000 
#define	DDSCAPS2_CUBEMAP_NEGATIVEZ 0x00008000 
#define	DDSCAPS2_VOLUME 0x00200000 

/* Next code was took from ddraw.h */

//
// For compilers that don't support nameless unions, do a
//
// #define NONAMELESSUNION
//
// before #include <ddraw.h>
//
#ifndef DUMMYUNIONNAMEN
#if defined(__cplusplus) || !defined(NONAMELESSUNION)
#define DUMMYUNIONNAMEN(n)
#else
#define DUMMYUNIONNAMEN(n)      u##n
#endif
#endif

typedef struct _DDSCAPS2
{
    DWORD       dwCaps;         // capabilities of surface wanted
    DWORD       dwCaps2;
    DWORD       dwCaps3;
    union
    {
        DWORD       dwCaps4;
        DWORD       dwVolumeDepth;
    } DUMMYUNIONNAMEN(1);
} DDSCAPS2;

typedef struct _DDPIXELFORMAT
{
    DWORD       dwSize;                 // size of structure
    DWORD       dwFlags;                // pixel format flags
    DWORD       dwFourCC;               // (FOURCC code)
    union
    {
        DWORD   dwRGBBitCount;          // how many bits per pixel
        DWORD   dwYUVBitCount;          // how many bits per pixel
        DWORD   dwZBufferBitDepth;      // how many total bits/pixel in z buffer (including any stencil bits)
        DWORD   dwAlphaBitDepth;        // how many bits for alpha channels
        DWORD   dwLuminanceBitCount;    // how many bits per pixel
        DWORD   dwBumpBitCount;         // how many bits per "buxel", total
        DWORD   dwPrivateFormatBitCount;// Bits per pixel of private driver formats. Only valid in texture
                                        // format list and if DDPF_D3DFORMAT is set
    } DUMMYUNIONNAMEN(1);
    union
    {
        DWORD   dwRBitMask;             // mask for red bit
        DWORD   dwYBitMask;             // mask for Y bits
        DWORD   dwStencilBitDepth;      // how many stencil bits (note: dwZBufferBitDepth-dwStencilBitDepth is total Z-only bits)
        DWORD   dwLuminanceBitMask;     // mask for luminance bits
        DWORD   dwBumpDuBitMask;        // mask for bump map U delta bits
        DWORD   dwOperations;           // DDPF_D3DFORMAT Operations
    } DUMMYUNIONNAMEN(2);
    union
    {
        DWORD   dwGBitMask;             // mask for green bits
        DWORD   dwUBitMask;             // mask for U bits
        DWORD   dwZBitMask;             // mask for Z bits
        DWORD   dwBumpDvBitMask;        // mask for bump map V delta bits
        struct
        {
            WORD    wFlipMSTypes;       // Multisample methods supported via flip for this D3DFORMAT
            WORD    wBltMSTypes;        // Multisample methods supported via blt for this D3DFORMAT
        } MultiSampleCaps;

    } DUMMYUNIONNAMEN(3);
    union
    {
        DWORD   dwBBitMask;             // mask for blue bits
        DWORD   dwVBitMask;             // mask for V bits
        DWORD   dwStencilBitMask;       // mask for stencil bits
        DWORD   dwBumpLuminanceBitMask; // mask for luminance in bump map
    } DUMMYUNIONNAMEN(4);
    union
    {
        DWORD   dwRGBAlphaBitMask;      // mask for alpha channel
        DWORD   dwYUVAlphaBitMask;      // mask for alpha channel
        DWORD   dwLuminanceAlphaBitMask;// mask for alpha channel
        DWORD   dwRGBZBitMask;          // mask for Z channel
        DWORD   dwYUVZBitMask;          // mask for Z channel
    } DUMMYUNIONNAMEN(5);
} DDPIXELFORMAT;


typedef struct _DDCOLORKEY
{
    DWORD       dwColorSpaceLowValue;   // low boundary of color space that is to
                                        // be treated as Color Key, inclusive
    DWORD       dwColorSpaceHighValue;  // high boundary of color space that is
                                        // to be treated as Color Key, inclusive
} DDCOLORKEY;

typedef struct _DDSURFACEDESC2
{
    DWORD               dwSize;                 // size of the DDSURFACEDESC structure
    DWORD               dwFlags;                // determines what fields are valid
    DWORD               dwHeight;               // height of surface to be created
    DWORD               dwWidth;                // width of input surface
    union
    {
        LONG            lPitch;                 // distance to start of next line (return value only)
        DWORD           dwLinearSize;           // Formless late-allocated optimized surface size
    } DUMMYUNIONNAMEN(1);
    union
    {
        DWORD           dwBackBufferCount;      // number of back buffers requested
        DWORD           dwDepth;                // the depth if this is a volume texture 
    } DUMMYUNIONNAMEN(5);
    union
    {
        DWORD           dwMipMapCount;          // number of mip-map levels requestde
                                                // dwZBufferBitDepth removed, use ddpfPixelFormat one instead
        DWORD           dwRefreshRate;          // refresh rate (used when display mode is described)
        DWORD           dwSrcVBHandle;          // The source used in VB::Optimize
    } DUMMYUNIONNAMEN(2);
    DWORD               dwAlphaBitDepth;        // depth of alpha buffer requested
    DWORD               dwReserved;             // reserved
    LPVOID              lpSurface;              // pointer to the associated surface memory
    union
    {
        DDCOLORKEY      ddckCKDestOverlay;      // color key for destination overlay use
        DWORD           dwEmptyFaceColor;       // Physical color for empty cubemap faces
    } DUMMYUNIONNAMEN(3);
    DDCOLORKEY          ddckCKDestBlt;          // color key for destination blt use
    DDCOLORKEY          ddckCKSrcOverlay;       // color key for source overlay use
    DDCOLORKEY          ddckCKSrcBlt;           // color key for source blt use
    union
    {
        DDPIXELFORMAT   ddpfPixelFormat;        // pixel format description of the surface
        DWORD           dwFVF;                  // vertex format description of vertex buffers
    } DUMMYUNIONNAMEN(4);
    DDSCAPS2            ddsCaps;                // direct draw surface capabilities
    DWORD               dwTextureStage;         // stage in multitexture cascade
} DDSURFACEDESC2;
/*End of code of ddraw.h header*/
#endif

#define DDS_ERR_NOTOPEN				-1
#define DDS_ERR_NOTDDS				-2
#define DDS_ERR_FILECORRUPTED		-3
#define DDS_ERR_FORMATNOTSUPPORTED	-4

#define RETURN_ERROR(x){iLastError=x; return x;}

GLint	iLastError=0;	
PFNGLCOMPRESSEDTEXIMAGE2DARBPROC glCompressedTexImage2DARB;

GLvoid DDS_Init(PFNGLCOMPRESSEDTEXIMAGE2DARBPROC fnCompressedLoad)
{
	glCompressedTexImage2DARB	=	fnCompressedLoad;
	return;
};
GLint	DDS_LoadFile(const GLchar *szFileName, GLuint &uiTexture, DDS_IMAGE_INFORMATION *pImgInfo)
{
	GLchar					*pFileData=0;
	GLuint					iFileSize=0;
	std::ifstream			inFile(szFileName,std::ios::beg|std::ios::binary);
	GLint					iResult=0;

	if(!inFile.is_open())
		RETURN_ERROR(DDS_ERR_NOTOPEN);
	inFile.seekg(0,std::ios::end);
	iFileSize=inFile.tellg();
	//
	pFileData=new GLchar[iFileSize];
	inFile.seekg(std::ios::beg, 0);
	inFile.read((char*)pFileData, iFileSize);
	inFile.close();
	iResult =DDS_Load(pFileData, uiTexture, pImgInfo);
	delete[] pFileData;
	pFileData=0;
	return iResult;

};
GLint	DDS_Load(const GLchar *pData, GLuint &uiTexture, DDS_IMAGE_INFORMATION *pImgInfo)
{
	const GLchar	*pFileData= pData;
	DDSURFACEDESC2	*ddsd;	//Description of surface, going after "DDS "
	DDS_IMAGE_INFORMATION	imgInfo;//Information about image
	DWORD	dwLinearSize=0;

	/*Firsts four bytes of any dds file must be D, D, S, <SPACE> ("DDS ")
	if it isn't - it is not a dds file									*/
	if(strncmp(pFileData, "DDS ", 4)!=0)
		RETURN_ERROR(DDS_ERR_NOTDDS);

	pFileData+=4;
	ddsd=(DDSURFACEDESC2*)pFileData;
	//* on this moment file will going throw the few tests for valid*/
	//if(ddsd->dwSize!=sizeof(DDSURFACEDESC2))
//		RETURN_ERROR(DDS_ERR_FILECORRUPTED);

	/*This flags including always! To all DDS-files */
	//if((ddsd->dwFlags&(DDSD_CAPS|DDSD_PIXELFORMAT|DDSD_WIDTH|DDSD_HEIGHT))==0)
	//	RETURN_ERROR(DDS_ERR_FILECORRUPTED);
	
	//if(ddsd->ddpfPixelFormat.dwSize!=sizeof(DDPIXELFORMAT))
	//	RETURN_ERROR(DDS_ERR_FILECORRUPTED);
	//if((ddsd->ddpfPixelFormat.dwFlags&(DDPF_FOURCC|DDPF_RGB))==0)
	//	RETURN_ERROR(DDS_ERR_FILECORRUPTED);
	//if((ddsd->dwFlags&DDPF_FOURCC)!=0)
	//	if(ddsd->ddpfPixelFormat.dwFourCC==FOURCC_DXT2)
	//		RETURN_ERROR(DDS_ERR_FORMATNOTSUPPORTED)
	//	else
	//		if(ddsd->ddpfPixelFormat.dwFourCC==FOURCC_DXT4)
	//			RETURN_ERROR(DDS_ERR_FORMATNOTSUPPORTED);
	
	//*Test completed. Now we can fill image's struct*/
	imgInfo.iComponents	=ddsd->ddpfPixelFormat.dwRGBBitCount/8;
	imgInfo.iHeight		=ddsd->dwHeight;
	imgInfo.iWidth		=ddsd->dwWidth;
	imgInfo.iNumMipMaps	=ddsd->dwMipMapCount;
	if(ddsd->dwFlags&DDPF_FOURCC)
	{
		if(ddsd->ddpfPixelFormat.dwFourCC==MAKEFOURCC('D','X','T','1'))
			imgInfo.iFormat=GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
		else
		if(ddsd->ddpfPixelFormat.dwFourCC==MAKEFOURCC('D','X','T','3'))
			imgInfo.iFormat=GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
		else
		if(ddsd->ddpfPixelFormat.dwFourCC==MAKEFOURCC('D','X','T','5'))
			imgInfo.iFormat=GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
	}else
		imgInfo.iFormat=GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
	
	if(ddsd->dwLinearSize==0)
	{
		switch(imgInfo.iFormat)
		{
		case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
			{
				dwLinearSize=imgInfo.iWidth*imgInfo.iHeight*4/8;
			};break;
		case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
			{
				dwLinearSize=imgInfo.iWidth*imgInfo.iHeight*4/4;
			};break;
		case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
			{
				dwLinearSize=imgInfo.iWidth*imgInfo.iHeight*4/4;
			};break;
		};
	}else dwLinearSize=ddsd->dwLinearSize;
	if(ddsd->dwFlags&DDSD_DEPTH)
		imgInfo.uiType=GL_TEXTURE_3D;
	else
		if((ddsd->ddsCaps.dwCaps2&DDSCAPS2_CUBEMAP))
			imgInfo.uiType=GL_TEXTURE_CUBE_MAP_ARB;
		else
			imgInfo.uiType=GL_TEXTURE_2D;
	
	/*For begin, loader for simple 2d-texture*/
	switch(imgInfo.uiType)
	{
	case GL_TEXTURE_2D:
		{
			pFileData+=sizeof(DDSURFACEDESC2);
			/*To do: glGenTextures, Bind, and FREE MEMORY "data*/
			glGenTextures(1, &uiTexture);
			glBindTexture(GL_TEXTURE_2D, uiTexture);
			glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glCompressedTexImage2DARB(GL_TEXTURE_2D, 0, 
				imgInfo.iFormat, imgInfo.iWidth, 
				imgInfo.iHeight, 0, dwLinearSize, pFileData);

		};break;
	};
	if(pImgInfo!=0)
		memcpy(pImgInfo, &imgInfo, sizeof(DDS_IMAGE_INFORMATION));
	pFileData=0;
	return 0;
};

GLint	DDS_GetLastError()
{
	return iLastError;
};